﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace System.Threading.Workers
{
  public abstract class QueueWorker<T> : Worker
  {
    #region Fields
    private bool _isWaiting = false;
    private Queue<T> _innerQueue = new Queue<T>();
    private Queue<T> _innerQueue2 = new Queue<T>();  //Double buffer.
    private Queue<T> _readQueue;
    private Queue<T> _writeQueue;
    private bool _switch = false;
    private volatile int _syncCount = 0;
    bool _isStopping = false;
    private object _sync = new object();
    private object _finishSync = new object();
    private ManualResetEvent _workingHandle = new ManualResetEvent(false);
    #endregion

    #region Constructors
    public QueueWorker(string name) : base(name)
    {
      _readQueue = _innerQueue2;
      _writeQueue = _innerQueue;
    }

    #endregion

    #region Methods
    private object _other = new object();
    public void WaitForCompletion(bool stopWhenComplete)
    {
      if (stopWhenComplete)
        _isStopping = true;

      _workingHandle.WaitOne();

      lock (_sync)
        Monitor.PulseAll(_sync);

      if (stopWhenComplete)
        Stop();
    }

    public override bool Stop(TimeSpan timeout, bool kill)
    {
      _isStopping = true;
      lock (_sync)
        Monitor.PulseAll(_sync);
      return base.Stop(timeout, kill);
    }

    public int QueueSize
    {
      get { return _innerQueue.Count; }
    }

    protected int Enqueue(T item)
    {
      //int count = 0;

      lock(_sync)
      {
        _writeQueue.Enqueue(item);
        //Interlocked.Increment(ref _syncCount);
        _syncCount++;
        Monitor.Pulse(_sync);
      }

      //WaitHandle.Set();
      return _syncCount;
    }
    #endregion

    #region Abstract Declarations
    
    public abstract void PerformWork(T item);
    #endregion

    #region Overrides
    protected override bool CanPerformWork
    {
      get
      {
        return true;
      }
    }

    protected override void OnStopped(EventArgs e)
    {
      lock (_innerQueue)
        _innerQueue.Clear();
      base.OnStopped(e);
    }

    protected override void PerformWork()
    {
      while (!_isStopping)
      {
        lock (_sync)
        {
          _workingHandle.Set();
          if (_writeQueue.Count == 0)
            Monitor.Wait(_sync);

          if (_switch)
          {
            _readQueue = _innerQueue2;
            _writeQueue = _innerQueue;
          }
          else
          {
            _readQueue = _innerQueue;
            _writeQueue = _innerQueue2;
          }
          _switch = !_switch;

          //Console.WriteLine("Switch");
          _workingHandle.Reset();
        }

        EmptyQueue();

      }

      //if (_innerQueue.Count > 0)
      //  EmptyQueue(_innerQueue);
      //if (_innerQueue2.Count > 0)
      //  EmptyQueue(_innerQueue2);

      if(_isWaiting)
        lock (_finishSync)
          Monitor.Pulse(_finishSync);
    }

    private void EmptyQueue()
    {
      T item = default(T);

      while (_readQueue.Count > 0)
      {
        item = _readQueue.Dequeue();
        //Interlocked.Decrement(ref _syncCount);
        _syncCount--;

        DateTime started = DateTime.Now;
        try
        {
          PerformWork(item);
        }
        catch (Exception ex)
        {
          OnError(ex, item);
        }

        OnWorkPerformed(DateTime.Now - started, item);
      }
    }
    #endregion

    #region Events

    #region WorkPerformedEventArgs Subclass
    public class WorkPerformedEventArgs : EventArgs
    {
      #region Fields
      private readonly TimeSpan _duration;
      private readonly T _item;
      #endregion
      #region Properties
      public T Item
      {
        get { return _item; }
      }

      public TimeSpan Duration
      {
        get { return _duration; }
      }
      #endregion
      #region Constructors
      internal WorkPerformedEventArgs(TimeSpan duration, T item)
      {
        #region Validation
        if (duration == null)
          throw new ArgumentNullException("duration");
        if (item == null)
          throw new ArgumentNullException("item");
        #endregion
        _duration = duration;
        _item = item;
      }
      #endregion
    }
    #endregion

    public new event EventHandler<WorkPerformedEventArgs> WorkPerformed;

    protected void OnWorkPerformed(TimeSpan duration, T item)
    {
      OnWorkPerformed(new WorkPerformedEventArgs(duration, item));
    }

    protected virtual void OnWorkPerformed(WorkPerformedEventArgs e)
    {
      if (WorkPerformed != null)
        WorkPerformed(this, e);
    }


    #region ErrorEventArgs Subclass
    public class ErrorEventArgs : EventArgs
    {
      #region Fields
      private readonly Exception _exception;
      private readonly T _item;
      #endregion
      #region Properties
      public T Item
      {
        get { return _item; }
      }
      
      public Exception Exception
      {
        get { return _exception; }
      }
      #endregion
      #region Constructors
      internal ErrorEventArgs(Exception exception, T item)
      {
        #region Validation
        if (exception == null)
          throw new ArgumentNullException("exception");
        #endregion
        _exception = exception;
        _item = item;
      }
      #endregion
    }
    #endregion

    public event EventHandler<ErrorEventArgs> Error;

    protected void OnError(Exception exception, T item)
    {
      OnError(new ErrorEventArgs(exception, item));
    }

    protected virtual void OnError(ErrorEventArgs e)
    {
      if (Error != null)
        Error(this, e);
    }





    #endregion
  }
}
